﻿package net.chrisjrock.loader.bulletloader
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.media.Sound;
	import flash.net.URLLoader;
	import flash.utils.Dictionary;
	
	/**
	 * ...
	 * @author Christopher J. Rock
	 */
	public class BulletRepo
	{
		public static var _eventDispatcher:EventDispatcher = new EventDispatcher();
		private static var _urlListByLoader:Dictionary = new Dictionary();
		private static var _resourceByUrl:Dictionary = new Dictionary();
		private static var _loaderByUrl:Dictionary = new Dictionary();
		private static var _eventTypeSuffixByUrl:Dictionary = new Dictionary();
		private static var _useCountByUrl:Dictionary = new Dictionary();
		
		public static var libraryDelimiter:String = "|";
		
		public static var deliverLoaderOnContentError:Boolean;
		public static var deliverImagesAsBitmapData:Boolean;
		private static var _resource:*;
		private static var _resourceQueue:Array;
		
		public static const RESOURCE_AVAILABLE:String = "resourceAvailable";
		public static const RESOURCE_UNAVAILABLE:String = "resourceUnavailable";
		public static const RESOURCE_DELETED:String = "resourceDeleted";
		
		//TODO: Use urlLists more effectively.
		
		public static function deliverResource ():* { return _resource; }
		
		private static function publishResource (resource:* , url:String):void {
			if (_resource)
			{
				if (!_resourceQueue)
				{
					_resourceQueue = [];
				}
				
				if (_resourceQueue.indexOf(url) < 0)
				{
					_resourceQueue[_resourceQueue.length] = url;
				}
				return;
			}
			
			_resource = getDefinition(resource , url); //Only applies if the url includes the library delimiter, otherwise it returns the resource.
			
			var eventType:String = RESOURCE_AVAILABLE;
			var eventTypeSuffix:String = _eventTypeSuffixByUrl[url];
			if (eventTypeSuffix != null)
			{
				eventType += eventTypeSuffix;
			}
			
			_eventDispatcher.dispatchEvent(new BulletEvent(eventType , url , url));
			delete _eventTypeSuffixByUrl[url];
			_resource = null;
			
			if (_resourceQueue)
			{
				var resourceQueue:Array = _resourceQueue;
				_resourceQueue = null;
				var loadUrl:String;
				for (var i:int ; i < resourceQueue.length ; i++)
				{
					url = resourceQueue[i];
					loadUrl = getLoadUrl(url);
					publishResource(_resourceByUrl[loadUrl] , url);
				}
			}
		}
		
		private static function getDefinition (resource:* , url:String):*
		{
			var index:int = url.indexOf(libraryDelimiter);
			if (index >= 0 && resource && resource is Sprite)
			{
				var definitionName:String = url.slice(index + 1);
				if (!resource.loaderInfo.applicationDomain.hasDefinition(definitionName))
				{
					_eventDispatcher.dispatchEvent(new BulletEvent(RESOURCE_UNAVAILABLE , url , url , "BulletRepo: Definition '" + definitionName + "' not found in '" + url.slice(0 , index) + "'. Resource url: " + url));
					delete _eventTypeSuffixByUrl[url];
					return ;
				}
				resource = resource.loaderInfo.applicationDomain.getDefinition(definitionName);
			}
			
			return resource;
		}
		
		public static function getDisplay (url:String , eventTypeSuffix:String = null):void {
			var loadUrl:String = getLoadUrl(url);
			
			if (eventTypeSuffix && eventTypeSuffix.length) _eventTypeSuffixByUrl[url] = eventTypeSuffix;
			if (_resourceByUrl[loadUrl]) publishResource(_resourceByUrl[loadUrl] , url);
			else if (!_loaderByUrl[loadUrl]) {
				var loader:Loader = _loaderByUrl[loadUrl] = BulletLoader.loadDisplay(loadUrl , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
				_urlListByLoader[loader.contentLoaderInfo] = [url];
			}else {
				_urlListByLoader[_loaderByUrl[loadUrl].contentLoaderInfo].push(url);
			}
		}
		
		public static function getData (url:String , eventTypeSuffix:String = null):void {
			var loadUrl:String = getLoadUrl(url);
			
			if (loadUrl.substr(loadUrl.length - 3) == ".swf")
			{
				getDisplay(url , eventTypeSuffix);
				return ;
			}
			
			if (eventTypeSuffix && eventTypeSuffix.length) _eventTypeSuffixByUrl[url] = eventTypeSuffix;
			if (_resourceByUrl[loadUrl]) publishResource(_resourceByUrl[loadUrl] , url);
			else if (!_loaderByUrl[loadUrl]) {
				var loader:URLLoader = _loaderByUrl[loadUrl] = BulletLoader.loadData(loadUrl , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
				_urlListByLoader[loader] = [url];
			}else {
				_urlListByLoader[_loaderByUrl[loadUrl]].push(url);
			}
		}
		
		public static function getSound (url:String , eventTypeSuffix:String = null):void {
			var loadUrl:String = getLoadUrl(url);
			
			if (loadUrl.substr(loadUrl.length - 4) == ".swf")
			{
				getDisplay(url , eventTypeSuffix);
				return ;
			}
			
			if (eventTypeSuffix && eventTypeSuffix.length) _eventTypeSuffixByUrl[url] = eventTypeSuffix;
			if (_resourceByUrl[loadUrl]) publishResource(_resourceByUrl[loadUrl] , url);
			else if (!_loaderByUrl[loadUrl]) {
				var loader:Sound = _loaderByUrl[loadUrl] = BulletLoader.loadSound(loadUrl , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
				_urlListByLoader[loader] = [url];
			}else {
				_urlListByLoader[_loaderByUrl[loadUrl]].push(url);
			}
		}
		
		/*public static function getVideo (url:String):void {
			
		}*/
		
		private static function onResourceLoaded (event:Event):void {
			BulletLoader.removeLoaderListeners(IEventDispatcher(event.target) , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
			
			var urlList:Array = _urlListByLoader[event.target];
			if (!urlList)
			{
				throw (new Error("BulletRepo: urlList not found for loaded asset."));
			}
			
			if (!urlList.length)
			{
				throw (new Error("BulletRepo: urlList has a length of 0."));
			}
			
			var loadUrl:String = getLoadUrl(urlList[0]);
			var publishLoader:Boolean;
			
			if (!_resourceByUrl[loadUrl]){
				var resource:*;
				
				var message:String;
				var eventTypeSuffix:String;
				if (event.target is URLLoader)
				{
					resource = event.target.data;
				}
				else if (event.target is Sound)
				{
					resource = event.target;
				}
				else if (event.target is LoaderInfo)
				{
					try
					{
						resource = event.target.content;
					}
					catch (error:Error)
					{
						if (deliverLoaderOnContentError)
						{
							resource = LoaderInfo(event.target).loader;
							publishLoader = true;
							message = "BulletRepo: Resource unavailable: " + error.message;
						}
					}
				}
				
				if (deliverImagesAsBitmapData && resource is Bitmap) {
					resource = Bitmap(resource).bitmapData;
				}
				
				if (resource && !publishLoader)
				{
					_resourceByUrl[loadUrl] = resource;
				}
				
				delete _loaderByUrl[loadUrl];
				delete _urlListByLoader[event.target];
				
				var url:String;
				var urlNum:int;
				var urlsTotal:int = urlList.length;
				for (; urlNum < urlsTotal ; urlNum++)
				{
					url = urlList[urlNum];
					
					if (resource && (!publishLoader || url == loadUrl))
					{
						publishResource(resource , url);
						continue;
					}
					
					eventTypeSuffix = _eventTypeSuffixByUrl[url];
					_eventDispatcher.dispatchEvent(new BulletEvent(RESOURCE_UNAVAILABLE + eventTypeSuffix , url , url , message));
				}
			}
		}
		
		private static function onResourceError (event:*):void {
			BulletLoader.removeLoaderListeners(IEventDispatcher(event.target) , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
			
			var urlList:Array = _urlListByLoader[event.target];
			var url:String = getLoadUrl(urlList[0]);
			delete _loaderByUrl[url];
			delete _urlListByLoader[event.target];
			var message:String = "BulletRepo: Resource unavailable: " + event.text;
			_eventDispatcher.dispatchEvent(new BulletEvent(RESOURCE_UNAVAILABLE , url , url , message));
			
			var eventTypeSuffix:String;
			for (var i:int ; i < urlList.length ; i++)
			{
				url = urlList[i];
				eventTypeSuffix = _eventTypeSuffixByUrl[url];
				_eventDispatcher.dispatchEvent(new BulletEvent(RESOURCE_UNAVAILABLE + eventTypeSuffix , url , url , message));
			}
		}
		
		public static function setResource (resource:* , url:String):Boolean {
			if (!_resourceByUrl[url] && !_loaderByUrl[url]) {
				_resourceByUrl[url] = resource;
				return true;
			}
			return false;
		}
		
		public static function overwriteResource (resource:* , url:String):void {
			deleteResource(url);
			_resourceByUrl[url] = resource;
		}
		
		public static function hasResource (url:String):Boolean {
			return getDefinition(_resourceByUrl[getLoadUrl(url)] , url) != null;
		}
		
		public static function getResource (url:String):* {
			return getDefinition(_resourceByUrl[getLoadUrl(url)] , url);
		}
		
		public static function deleteResource (url:String):Boolean {
			if (getLoadUrl(url) != url)
			{
				throw (new Error("BulletRepo: Cannot delete a subresource. Use the getLoadUrl method to find and delete the asset's parent."));
			}
			
			delete _useCountByUrl[url];
			
			if (_resourceByUrl[url]) {
				delete _resourceByUrl[url];
				_eventDispatcher.dispatchEvent(new BulletEvent(RESOURCE_DELETED , url , url));
				return true;
			}
			
			if (_loaderByUrl[url]) {
				var urlList:Array = _urlListByLoader[_loaderByUrl[url]];
				delete _urlListByLoader[_loaderByUrl[url]];
				
				if (_loaderByUrl[url]) {
					BulletLoader.removeLoaderListeners(_loaderByUrl[url] , onResourceLoaded , onResourceError , null , null , onResourceError , onResourceError);
					_loaderByUrl[url].close();
					delete _loaderByUrl[url];
				}
				
				var eventType:String;
				var eventTypeSuffix:String;
				
				var urlNum:int;
				var urlsTotal:int;
				for (; urlNum < urlsTotal ; urlNum++)
				{
					eventTypeSuffix = _eventTypeSuffixByUrl[url];
					delete _eventTypeSuffixByUrl[url];
					
					eventType = RESOURCE_DELETED;
					if (eventTypeSuffix != null)
					{
						eventType += eventTypeSuffix;
					}
					
					_eventDispatcher.dispatchEvent(new BulletEvent(eventType , url , url));
				}
				
				return true;
			}
			
			return false;
		}
		
		public static function getLoadUrl (url:String):String
		{
			if (url)
			{
				var index:int = url.indexOf(libraryDelimiter);
				if (index >= 0)
				{
					return url.slice(0 , index);
				}
			}
			
			return url;
		}
		
		public static function getUseCount (url:String):int {
			return int(_useCountByUrl[url]);
		}
		
		public static function incrementUseCount (url:String , increment:int):int {
			if (_useCountByUrl[url] is int)
			{
				return _useCountByUrl[url] += increment;
			}
			
			return _useCountByUrl[url] = increment;
		}
		
		public static function decrementUseCount (url:String , decrement:int):int {
			if (int(_useCountByUrl[url]) > decrement)
			{
				return _useCountByUrl[url] -= decrement;
			}
			return _useCountByUrl[url] = 0;
		}
		
		public static function getUseCounts ():Dictionary
		{
			var useCounts:Dictionary = new Dictionary();
			for (var url:String in _useCountByUrl)
			{
				useCounts[url] = _useCountByUrl[url];
			}
			return useCounts;
		}
		
		public static function cullUnusedResources ():void
		{
			var deletedUrls:Dictionary = new Dictionary();
			var loadUrl:String;
			for (var url:String in _useCountByUrl)
			{
				if (_useCountByUrl[url] > 0)
				{
					continue;
				}
				
				loadUrl = getLoadUrl(url);
				if (deletedUrls[loadUrl] == null)
				{
					deleteResource(loadUrl);
					deletedUrls[loadUrl] = true;
				}
			}
		}
		
		public static function getRandomSuffix ():String
		{
			return uint(uint.MAX_VALUE * Math.random()).toString();
		}
		
		public static function get eventDispatcher ():EventDispatcher
		{
			return _eventDispatcher;
		}
	}
	
}